package com.metadata_system.system.service;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.aliyun.oss.ServiceException;
import com.metadata_system.common.db.entity.*;
import com.metadata_system.common.db.mapper.*;
import com.metadata_system.common.dto.ApiResult;
import com.metadata_system.common.dto.ProjectLogChangeItem;
import com.metadata_system.common.service.CommonUserService;
import com.metadata_system.common.service.ExcelReaderUtil;
import com.metadata_system.common.service.UserOperationLogService;
import com.metadata_system.common.utils.Base62Encoder;
import com.metadata_system.common.utils.DateTimeUtil;
import com.metadata_system.common.utils.MyUtil;
import com.metadata_system.system.request.ProjectLogListParams;
import com.metadata_system.system.response.NodePropertiesItem;
import com.metadata_system.system.response.ProjectLogResponse;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import tk.mybatis.mapper.entity.Example;

import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ProjectLogService {
    @Autowired(required = false)
    ProjectNodeMapper projectNodeMapper;
    @Autowired(required = false)
    MetadataNodeMapper metadataNodeMapper;

    @Autowired(required = false)
    ProjectLogMapper projectLogMapper;

    @Autowired(required = false)
    ProjectUserMapper projectUserMapper;

    @Autowired(required = false)
    UserMapper userMapper;

    @Value("${metadata-system.upload-path:/www/wwwroot/upload-file.metadata-system.cc/}")
    private String uploadPath;

    @Autowired
    MessageService messageService;

    @Autowired
    UserOperationLogService userOperationLogService;


    /**
     * 获取项目日志列表
     *
     * @param listParams
     * @return
     */
    public ApiResult getList(ProjectLogListParams listParams) {
        Example example = new Example(ProjectLog.class);
        Example.Criteria criteria = example.createCriteria();

        if (listParams.getUserId() == null || listParams.getUserId() == 0) {
            return ApiResult.error("用户ID 必须");
        }


        // 我申请的
        if (listParams.getShowType().equals("apply")) {
            criteria.andEqualTo("userId", listParams.getUserId());
            if (!StringUtils.isBlank(listParams.getReadStatus())) {
                criteria.andEqualTo("isUserRead", listParams.getReadStatus().equals("unRead") ? 0 : 1);
            }
        }

        // 我处理的(非取消掉的)
        if (listParams.getShowType().equals("handler")) {
            criteria.andEqualTo("handlerUserId", listParams.getUserId());
            criteria.andNotEqualTo("status", 3);
            if (!StringUtils.isBlank(listParams.getReadStatus())) {
                criteria.andEqualTo("isHandlerUserRead", listParams.getReadStatus().equals("unRead") ? 0 : 1);
            }
        }


        if (listParams.getProjectId() != null && listParams.getProjectId() > 0) {
            criteria.andEqualTo("projectId", listParams.getProjectId());
        }
        if (listParams.getStatus() != null) {
            criteria.andEqualTo("status", listParams.getStatus());
        }
        if (listParams.getType() != null) {
            criteria.andEqualTo("type", listParams.getType());
        }

        if (!StringUtils.isBlank(listParams.getProjectNodeNames())) {
            criteria.andCondition("FIND_IN_SET('" + listParams.getProjectNodeNames() + "',project_node_names)");
        }

        listParams.setOrder(example, "createdTime");
        listParams.setConditionTime(listParams.getCreatedTime(), criteria, "createdTime");

        int count = this.projectLogMapper.selectCountByExample(example);
        List<ProjectLog> list = new ArrayList<>();
        if (count > 0) {
            list = this.projectLogMapper.selectByExampleAndRowBounds(example, listParams.getRowRunds());
        }

        List<ProjectLogResponse> projectLogResponses = ProjectLogResponse.formatData(list);
        setListOhterInfo(projectLogResponses);
        return ApiResult.successPage(projectLogResponses, count);

    }

    @Autowired(required = false)
    ProjectMapper projectMapper;

    /**
     * 设置列表其他信息
     *
     * @param list
     */
    public void setListOhterInfo(List<ProjectLogResponse> list) {
        if (list.isEmpty()) {
            return;
        }

        List<Integer> logIds = list.stream().map(ProjectLogResponse::getId).collect(Collectors.toList());
        List<Integer> userIds = list.stream().map(ProjectLogResponse::getUserId).collect(Collectors.toList());
        List<Integer> handlerUserIds = list.stream().map(ProjectLogResponse::getHandlerUserId).collect(Collectors.toList());
        userIds.addAll(handlerUserIds);

        Map<Integer, User> userMap = new HashMap<>();
        Map<Integer, List<ProjectLogHistory>> historyMap = new HashMap<>();
        for (ProjectLogResponse projectLogResponse : list) {
            historyMap.put(projectLogResponse.getId(), new ArrayList<>());
        }


        // 用户信息
        Example userExample = new Example(User.class);
        userExample.createCriteria().andIn("id", userIds);
        List<User> userList = userMapper.selectByExample(userExample);
        userList.forEach(item -> {
            userMap.put(item.getId(), item);
        });


        // 项目日志历史
        Example projectLogHistoryExample = new Example(ProjectLogHistory.class);
        projectLogHistoryExample.createCriteria().andIn("projectLogId", logIds);
        List<ProjectLogHistory> projectLogHistoryList = projectLogHistoryMapper.selectByExample(projectLogHistoryExample);
        projectLogHistoryList.forEach(item -> {
            historyMap.get(item.getProjectLogId()).add(item);
        });


        for (ProjectLogResponse projectLogResponse : list) {
            User user = userMap.get(projectLogResponse.getUserId());
            if (user != null) {
                projectLogResponse.setUsername(user.getUsername());
                projectLogResponse.setUserAvatar(user.getAvatar());
            }

            User handlerUser = userMap.get(projectLogResponse.getHandlerUserId());
            if (handlerUser != null) {
                projectLogResponse.setHandlerUsername(handlerUser.getUsername());
                projectLogResponse.setHandlerUserAvatar(handlerUser.getAvatar());
            }
        }

        log.info("historyMap: " + JSON.toJSONString(historyMap));

        list.forEach(item -> {
            log.info("historyMap.get(item.getId()): " + JSON.toJSONString(historyMap.get(item.getId())));
            item.setHistoryList(historyMap.get(item.getId()));
        });

    }

    /**
     * 提交导入申请
     *
     * @param jsonData
     * @param userId
     * @param projectId
     * @return
     */
    @SneakyThrows
    public ApiResult submitImport(String jsonData, Integer userId, Integer projectId) {
        // 检查项目权限
        Project project = this.checkProjectPower(projectId, userId, "Editor");
        if (project == null) {
            return ApiResult.error("项目不存在");
        }

        List<ExcelReaderUtil.ExcelSheetData> excelSheetDataList = JSON.parseArray(jsonData, ExcelReaderUtil.ExcelSheetData.class);
        if (excelSheetDataList.isEmpty()) {
            return ApiResult.error("导入数据不能为空");
        }
        String saveJsonFilePath = "/import-file/" + Base62Encoder.generateUniqueKey() + ".json";
        List<String> projectNodeNemes = new ArrayList<>();
        Integer dataTotal = 0;
        // 1.检查节点： 名称，属性列表
        for (ExcelReaderUtil.ExcelSheetData excelSheetData : excelSheetDataList) {
            MetadataNode currNode = metadataNodeMapper.selectOne(MetadataNode.builder().metadataId(project.getMetadataId()).name(excelSheetData.getName()).build());
            if (currNode == null) {
                return ApiResult.error("节点 【" + excelSheetData.getName() + "】 不存在");
            }
            String propertiesKey = ProjectNode.getPropertiesKeyByNames(excelSheetData.getHeaders(), excelSheetData.getName());
            if (!propertiesKey.equalsIgnoreCase(currNode.getPropertiesKey())) {
                return ApiResult.error("节点 【" + excelSheetData.getName() + "】 属性不匹配");
            }
            projectNodeNemes.add(excelSheetData.getName());
        }

        String dataKey = DigestUtils.md5DigestAsHex(jsonData.getBytes());


        // 2.检查是否有正在执行的
        int count = this.projectLogMapper.selectCount(ProjectLog.builder()
                .projectId(projectId)
                .dataKey(dataKey)
                .userId(userId)
                .isRollBack(0)
                .status(1)
                .build());
        if (count > 0) {
            return ApiResult.error("已经有相同数据的导入申请");
        }

        log.info("projectNodeNemes: "+projectNodeNemes);


        Integer belongUserId = this.projectUserMapper.getBelongUserId(projectId);
        // 3.插入记录
        projectLogMapper.insertSelective(ProjectLog.builder()
                .userId(userId)
                .projectName(project.getName())
                .projectId(projectId)
                .projectNodeNames(MyUtil.join(projectNodeNemes, ","))
                .type("Import")
                .status(0)
                .refuseReason("")
                .file(saveJsonFilePath)
                .isUserRead(1)
                .handlerUserId(belongUserId)
                .isHandlerUserRead(0)
                .metadataId(project.getMetadataId())
                .metadataName(project.getMetadataName())
                .dataKey(dataKey)
                .createdTime(new Date())
                .build());


        // 4.记录json文件
        try (FileWriter writer = new FileWriter(uploadPath + saveJsonFilePath)) {
            writer.write(jsonData);
        } catch (IOException e) {
            return ApiResult.error("导入数据，写入文件时出错: " + e.getMessage());
        }

        // 5.发送消息
        this.messageService.addMessage(
                "您的有一个项目导入申请【" + project.getName() + "】，请及时处理",
                "ProjectHanlder",
                belongUserId,
                projectId,
                project.getName(),
                projectLogMapper.getLastInsertId().toString());


        // 6.添加日志历史
        this.addHistory(projectLogMapper.getLastInsertId(), userId, "SubmitImport", "申请导入数据: " + MyUtil.join(projectNodeNemes, ","));

        return ApiResult.success();
    }


    /**
     * 取消导入申请
     *
     * @param id
     * @param userId
     * @return
     */
    public ApiResult cancel(Integer id, Integer userId) {
        // 1.查询导入申请
        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .id(id)
                .userId(userId)
                .status(0)
                .build());
        if (projectLog == null) {
            return ApiResult.error("导入申请不存在");
        }

        // 2.更新取消状态
        this.projectLogMapper.updateByPrimaryKeySelective(ProjectLog.builder()
                .id(id)
                .status(3)
                .isHandlerUserRead(0)
                .build());

        this.messageService.addMessage(
                "您的项目【" + projectLog.getProjectName() + "】的导入申请已取消",
                "ProjectHandler",
                projectLog.getHandlerUserId(),
                projectLog.getProjectId(),
                projectLog.getProjectName(),
                projectLog.getId().toString());

        // 3.添加日志历史
        this.addHistory(id, userId, "CancelImport", "取消-导入数据申请");

        return ApiResult.success();
    }


    /**
     * 同意导入
     *
     * @param projectLogId
     * @param userId
     * @return
     */
    public ApiResult agree(Integer projectLogId, Integer userId) {

        // 1.查询导入申请
        ProjectLog projectLog = this.projectLogMapper.selectByPrimaryKey(1);
        if (projectLog == null) {
            return ApiResult.error("项目申请 不存在");
        }
        // 检查项目权限
        Project project = this.checkProjectPower(projectLog.getProjectId(), userId, "Owner");
        if (project == null) {
            return ApiResult.error("项目申请 不存在");
        }

        String actType = projectLog.getType().equals("Import") ? "导入" : "删除";

        // 2.同意导入申请
        this.projectLogMapper.updateByPrimaryKeySelective(ProjectLog.builder()
                .id(projectLogId)
                .status(1)
                .isLock(0)
                .isUserRead(0)
                .build());

        // 3.发送消息
        this.messageService.addMessage(
                "您的项目【" + projectLog.getProjectName() + "】的" + actType + "申请已同意,数据: " + projectLog.getProjectNodeNames(),
                "ProjectApply",
                projectLog.getUserId(),
                projectLog.getProjectId(),
                projectLog.getProjectName(),
                projectLog.getId().toString());

        // 4.添加日志历史
        this.addHistory(projectLogId, userId, "AgreeImport", "同意-" + actType + "数据申请: " + projectLog.getProjectNodeNames());

        // 5.导入数据
        if (projectLog.getType().equals("Import")) {
            this.insertAllData(projectLog, projectLog.getUserId());

            // 5.添加导入日志
            this.userOperationLogService.insert(UserOperationLog.builder()
                    .userId(userId)
                    .username(userMapper.selectByPrimaryKey(userId).getUsername())
                    .type("ProjectNodeData.Import")
                    .content(projectLog.getProjectName() + " 导入数据: " + projectLog.getProjectNodeNames())
                    .params(JSON.toJSONString(this.getMetadataNodeIdByNames(projectLog.getProjectId(), projectLog.getProjectNodeNames(), new ArrayList<>())))
                    .createdTime(new Date())
                    .build());
        }

        // 6.删除数据
        if (projectLog.getType().equals("Delete")) {
            List<ExcelReaderUtil.ExcelSheetData> excelSheetDataList = ExcelReaderUtil.getFileContent(uploadPath + projectLog.getFile());
            List<Integer> idList = new ArrayList<>();
            for (Map<String, Object> tableDatum : excelSheetDataList.get(0).getTableData()) {
                idList.add(Integer.parseInt(tableDatum.get("id").toString()));
            }
            // 6.1删除数据
            Example example = new Example(ProjectLog.class);
            example.createCriteria().andIn("id", idList);
            this.projectMapper.deleteAllData(excelSheetDataList.get(0).getTableName(), idList);

            // 6.2添加删除日志
            this.userOperationLogService.insert(UserOperationLog.builder()
                    .userId(userId)
                    .username(userMapper.selectByPrimaryKey(userId).getUsername())
                    .type("ProjectNodeData.Delete")
                    .content(projectLog.getProjectName() + " 删除数据: " + projectLog.getProjectNodeNames())
                    .params(JSON.toJSONString(this.getMetadataNodeIdByNames(projectLog.getProjectId(), projectLog.getProjectNodeNames(), idList)))
                    .createdTime(new Date())
                    .build());
        }

        this.updateDataTotal(projectLog.getId(), projectLog.getUserId());

        return ApiResult.success();
    }

    /**
     * 插入数据
     *
     * @param projectLog
     */
    public void insertAllData(ProjectLog projectLog, Integer userId) {
        // 1.当前导入文件内容
        List<ExcelReaderUtil.ExcelSheetData> excelSheetDataList = ExcelReaderUtil.getFileContent(uploadPath + projectLog.getFile());

        // 2.获取元数据所有节点
        List<MetadataNode> metadataNodeList = this.metadataNodeMapper.selectDistinctByUniqueKeyByMetadataId(projectLog.getMetadataId());
        Map<String, MetadataNode> metadataNodeMap = new HashMap<>();
        for (MetadataNode metadataNode : metadataNodeList) {
            metadataNodeMap.put(metadataNode.getName(), metadataNode);
        }

        // 3.遍历-检查创建表，插入数据
        for (ExcelReaderUtil.ExcelSheetData excelSheetData : excelSheetDataList) {
            MetadataNode metadataNode = metadataNodeMap.get(excelSheetData.getName());
            if (metadataNode == null) {
                throw new RuntimeException("元数据节点不存在");
            }
            // 3-1.检查创建表
            checkTable(projectLog.getProjectId(), metadataNode, userId);
            String tableName = "md_project_node_data_" + projectLog.getProjectId() + "_" + metadataNode.getId();

            // 增加项目日志ID，创建时间
            for (Map<String, Object> tableDatum : excelSheetData.getTableData()) {
                tableDatum.put("createdTime", DateTimeUtil.formatDateTimetoString(new Date(), DateTimeUtil.FMT_yyyyMMddHHmmss));
                tableDatum.put("projectLogId", projectLog.getId());
            }

            List<String> fieldList = new ArrayList<>();
            for (String field : excelSheetData.getTableData().get(0).keySet()) {
                fieldList.add(field);
            }

            // 3-2.插入导入数据
            projectMapper.insertAllData(tableName, fieldList, excelSheetData.getTableData());
        }
    }


    /**
     * 拒绝导入
     *
     * @param id
     * @param userId
     * @return
     */
    public ApiResult refuse(Integer id, Integer userId, String refuseReason) {
        // 1.查询导入申请
        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .id(id)
                .handlerUserId(userId)
                .status(0)
                .build());
        if (projectLog == null) {
            return ApiResult.error("导入申请不存在");
        }
        // 检查项目权限
        Project project = this.checkProjectPower(projectLog.getProjectId(), userId, "Owner");
        if (project == null) {
            return ApiResult.error("项目不存在");
        }

        // 2.拒绝导入
        this.projectLogMapper.updateByPrimaryKeySelective(ProjectLog.builder()
                .id(id)
                .status(2)
                .isUserRead(0)
                .isLock(0)
                .refuseReason(refuseReason)
                .build());

        // 3.发送消息
        this.messageService.addMessage(
                "您的项目【" + projectLog.getProjectName() + "】的导入申请已拒绝,拒绝原因: " + refuseReason,
                "ProjectApply",
                projectLog.getUserId(),
                projectLog.getProjectId(),
                projectLog.getProjectName(),
                projectLog.getId().toString());

        // 4.添加日志历史
        this.addHistory(id, userId, "RefuseImport", "拒绝-导入数据申请: " + projectLog.getProjectNodeNames() + "，拒绝原因: " + refuseReason);

        return ApiResult.success();
    }

    /**
     * 锁定数据
     *
     * @param id
     * @param userId
     * @param lockStatus
     * @return
     */
    public ApiResult lockItem(Integer id, Integer userId, Integer lockStatus) {

        // 1.查询导入申请
        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .id(id)
                .handlerUserId(userId)
                .status(0)
                .isLock(lockStatus == 1 ? 0 : 1)
                .build());
        if (projectLog == null) {
            return ApiResult.error("导入申请不存在");
        }
        // 检查项目权限
        Project project = this.checkProjectPower(projectLog.getProjectId(), userId, "Owner");
        if (project == null) {
            return ApiResult.error("项目不存在");
        }

        // 2.拒绝导入
        this.projectLogMapper.updateByPrimaryKeySelective(ProjectLog.builder()
                .id(id)
                .isLock(lockStatus)
                .build());


        // 3.添加日志历史
//        this.addHistory(id, userId, "LockImport", "锁定-导入数据申请: " + projectLog.getProjectNodeNames());
        return ApiResult.success();
    }


    /**
     * 重新申请导入
     *
     * @param jsonData
     * @param id
     * @param userId
     * @return
     */
    public ApiResult reSubmitImport(String jsonData, Integer id, Integer userId) {
        // 1.查询【被拒绝】导入申请
        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .userId(userId)
                .id(id)
                .isLock(0)
                .build());
        if (projectLog == null) {
            return ApiResult.error("导入申请 不存在1");
        }
        // 检查项目
        Project project = this.checkProjectPower(projectLog.getProjectId(), userId, "Editor");
        if (project == null) {
            return ApiResult.error("项目不存在");
        }

        // 如果不是: 未处理 或 已拒绝 状态，不能重新申请
        if (!(projectLog.getStatus() == 2 || projectLog.getStatus() == 0)) {
            return ApiResult.error("导入申请 不存在2");
        }

        // 2.解析json数据
        List<ExcelReaderUtil.ExcelSheetData> excelSheetDataList = JSON.parseArray(jsonData, ExcelReaderUtil.ExcelSheetData.class);
        String saveJsonFilePath = "/import-file/" + Base62Encoder.generateUniqueKey() + ".json";
        List<String> projectNodeNemes = new ArrayList<>();
        Integer dataTotal = 0;
        for (ExcelReaderUtil.ExcelSheetData excelSheetData : excelSheetDataList) {
            ProjectNode currNode = projectNodeMapper.selectOne(ProjectNode.builder().projectId(projectLog.getProjectId()).metadataNodeName(excelSheetData.getName()).build());
            if (currNode == null) {
                return ApiResult.error("节点 【" + excelSheetData.getName() + "】 不存在");
            }
            Map<String, Object> dataMap = excelSheetData.getTableData().get(0);
            log.info("keyList: " + JSON.toJSONString(excelSheetData.getHeaders()));
            String propertiesKey = ProjectNode.getPropertiesKeyByNames(excelSheetData.getHeaders(), excelSheetData.getName());
            if (!propertiesKey.equalsIgnoreCase(currNode.getMetadataNodePropertiesKey())) {
                return ApiResult.error("节点 【" + excelSheetData.getName() + "】 属性不匹配");
            }
            projectNodeNemes.add(excelSheetData.getName());
        }

        String dataKey = DigestUtils.md5DigestAsHex(jsonData.getBytes());

        // 3.更新导入申请
        this.projectLogMapper.updateByPrimaryKeySelective(ProjectLog.builder()
                .id(id)
                .projectNodeNames(MyUtil.join(projectNodeNemes, ","))
                .status(0)
                .dataKey(dataKey)
                .file(saveJsonFilePath)
                .isHandlerUserRead(0)
                .build());

        // 4.记录json文件
        try (FileWriter writer = new FileWriter(uploadPath + saveJsonFilePath)) {
            writer.write(jsonData);
        } catch (IOException e) {
            return ApiResult.error("导入数据，写入文件时出错: " + e.getMessage());
        }


        // 5.发送消息
        this.messageService.addMessage(
                "您的项目【" + projectLog.getProjectName() + "】的导入申请已重新提交",
                "ProjectApply",
                projectLog.getHandlerUserId(),
                projectLog.getProjectId(),
                projectLog.getProjectName(),
                projectLog.getId().toString());

        // 6.添加日志历史
        this.addHistory(projectLog.getId(), userId, "ReSubmitImport", "重新提交申请-导入数据: " + projectLog.getProjectNodeNames());

        return ApiResult.success();

    }


    /**
     * 删除日志
     *
     * @param id
     * @param userId
     * @return
     */
    public ApiResult delete(Integer id, Integer userId) {
        // 1.查询导入申请
        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .id(id)
                .userId(userId)
                .status(3)
                .build());
        if (projectLog == null) {
            return ApiResult.error("导入申请不存在");
        }
        // 2.删除导入申请
        this.projectLogMapper.deleteByPrimaryKey(id);
        return ApiResult.success();
    }

    /**
     * 获取数据详情
     *
     * @param id
     * @return
     */
    public ApiResult getDataDetail(Integer id, Integer userId) {
        // 1.查询导入申请
        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .id(id)
                .build());
        if (projectLog == null) {
            return ApiResult.error("导入申请 不存在");
        }
        if (!(projectLog.getProjectId().equals(userId) || projectLog.getHandlerUserId().equals(userId))) {
            return ApiResult.error("权限不足");
        }
        return ApiResult.success(ExcelReaderUtil.getFileContent(uploadPath + projectLog.getFile()));
    }

//    public static List<ExcelReaderUtil.ExcelSheetData> getFileContent(String filePath) {
//        // 1.读取文件内容
//        String fileContent = MyUtil.readFile(filePath);
//        if (StringUtils.isBlank(fileContent)) {
//            log.error("文件不存在: " + filePath);
//            throw new RuntimeException("文件内容 为空");
//        }
//        // 2.解析文件内容
//        List<ExcelReaderUtil.ExcelSheetData> excelSheetDataList = JSON.parseArray(fileContent, ExcelReaderUtil.ExcelSheetData.class);
//        if (CollectionUtils.isEmpty(excelSheetDataList)) {
//            throw new RuntimeException("文件内容 为空");
//        }
//        return excelSheetDataList;
//    }

    @Autowired
    CommonUserService commonUserService;


    /**
     * 读取单条日志
     *
     * @param id
     * @param userId
     * @return
     */
    public ApiResult readItem(Integer id, Integer userId) {
        // 1.查询导入申请
        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .id(id)
                .userId(userId)
                .build());
        if (projectLog == null) {
            return ApiResult.error("导入申请不存在");
        }
        this.projectLogMapper.updateByPrimaryKeySelective(ProjectLog.builder()
                .id(id)
                .isUserRead(1)
                .build());

        // 返回用户未读信息总数
        return commonUserService.getUnReadCount(userId);
    }


    /**
     * 处理读取日志
     *
     * @param id
     * @param userId
     * @return
     */
    public ApiResult handlerRead(Integer id, Integer userId) {
        // 1.查询导入申请
        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .id(id)
                .handlerUserId(userId)
                .build());
        if (projectLog == null) {
            return ApiResult.error("导入申请不存在");
        }
        this.projectLogMapper.updateByPrimaryKeySelective(ProjectLog.builder()
                .id(id)
                .isHandlerUserRead(1)
                .build());
        return this.commonUserService.getUnReadCount(userId);
    }


    /**
     * 提交单条日志
     *
     * @param projectId
     * @param nodeId
     * @param userId
     * @param jsonData
     * @return
     */
    public ApiResult submit(Integer projectId, Integer nodeId, Integer userId, String jsonData) {
        // 1.检查项目
        Project project = this.checkProjectPower(projectId, userId, "Editor");
        if (project == null) {
            return ApiResult.error("项目不存在");
        }

        List<ExcelReaderUtil.ExcelSheetData> excelSheetDataList = new ArrayList<>();
        excelSheetDataList.add(ExcelReaderUtil.ExcelSheetData.builder()
                .headers(new ArrayList<>())
                .tableFieldList(new ArrayList<>())
                .errorIndexList(new ArrayList<>())
                .errorPositionList(new ArrayList<>())
                .errorTotal(0)
                .tableData(new ArrayList<>())
                .build());

        Map<String, Object> dataMap = JSON.parseObject(
                jsonData,
                new TypeReference<Map<String, Object>>() {
                }
        );


        // 1.检查节点： 名称，属性列表
        MetadataNode currNode = metadataNodeMapper.selectByPrimaryKey(nodeId);
        if (currNode == null) {
            return ApiResult.error("节点 【" + nodeId + "】 不存在");
        }
        List<NodePropertiesItem> nodePropertiesItems = JSON.parseArray(currNode.getProperties(), NodePropertiesItem.class);
        HashMap<String, NodePropertiesItem> stringNodePropertiesItemHashMap = new HashMap<>();

        // 2.检查属性列表
        List<String> propList = nodePropertiesItems.stream().map(NodePropertiesItem::getProp).collect(Collectors.toList());
        List<String> mapPropList = new ArrayList<>();
        for (String key : dataMap.keySet()) {
            mapPropList.add(key);
        }
        mapPropList.sort(String::compareTo);
        propList.sort(String::compareTo);
        if (!MyUtil.join(propList, ",").equals(MyUtil.join(mapPropList, ","))) {
            return ApiResult.error("节点 【" + currNode.getName() + "】 属性 列表 与 节点定义 不一致");
        }

        // 3.检查属性值，类型
        for (NodePropertiesItem nodePropertiesItem : nodePropertiesItems) {
            stringNodePropertiesItemHashMap.put(nodePropertiesItem.getProp(), nodePropertiesItem);
        }
        for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
            NodePropertiesItem nodePropertiesItem = stringNodePropertiesItemHashMap.get(entry.getKey());
            if (nodePropertiesItem == null) {
                return ApiResult.error("节点 【" + currNode.getName() + "】 属性 【" + entry.getKey() + "】 不存在");
            }
            // 3.检查属性值
            if (nodePropertiesItem.getType().equals("boolean")) {
                if (!(entry.getValue() instanceof Boolean)) {
                    return ApiResult.error("节点 【" + currNode.getName() + "】 属性 【" + entry.getKey() + "】 类型错误，期望 boolean");
                }
            } else if (nodePropertiesItem.getType().equals("string")) {
                if (!(entry.getValue() instanceof String)) {
                    return ApiResult.error("节点 【" + currNode.getName() + "】 属性 【" + entry.getKey() + "】 类型错误，期望 string");
                }
                if (((String) entry.getValue()).length() > 200) {
                    return ApiResult.error("节点 【" + currNode.getName() + "】 属性 【" + entry.getKey() + "】 值长度不能超过 200 个字符");
                }
            } else if (nodePropertiesItem.getType().equals("number")) {
                try {
                    Double.valueOf(entry.getValue().toString());
                } catch (NumberFormatException e) {
                    return ApiResult.error("节点 【" + currNode.getName() + "】 属性 【" + entry.getKey() + "】 类型错误，期望 number");
                }
            }
        }


        excelSheetDataList.get(0).setIndex(0);
        excelSheetDataList.get(0).getTableData().add(dataMap);
        excelSheetDataList.get(0).setTableFieldList(nodePropertiesItems);
        excelSheetDataList.get(0).setTableName("md_project_node_data_" + projectId + "_" + currNode.getId());
        excelSheetDataList.get(0).setName(currNode.getName());

        List<String> headers = nodePropertiesItems.stream().map(NodePropertiesItem::getLabel).collect(Collectors.toList());
        headers = headers.stream()
                .filter(header -> !"ID".equals(header.toUpperCase()))
                .collect(Collectors.toList());
        excelSheetDataList.get(0).setHeaders(headers);
        excelSheetDataList.get(0).setHeaderKey(ProjectNode.getPropertiesKeyByNames(headers, currNode.getName()));


        String saveJsonFilePath = "/import-file/" + Base62Encoder.generateUniqueKey() + ".json";
        String dataKey = DigestUtils.md5DigestAsHex(jsonData.getBytes());

        this.projectLogMapper.insertSelective(ProjectLog.builder()
                .userId(userId)
                .projectName(project.getName())
                .projectId(projectId)
                .projectNodeNames(currNode.getName())
                .type("Import")
                .status(0)
                .refuseReason("")
                .file(saveJsonFilePath)
                .isUserRead(1)
                .handlerUserId(this.projectUserMapper.getBelongUserId(projectId))
                .isHandlerUserRead(0)
                .metadataName(project.getMetadataName())
                .metadataId(currNode.getMetadataId())
                .dataKey(dataKey)
                .createdTime(new Date())
                .build());

        // 5.添加日志历史
        Integer lastInsertId = this.projectLogMapper.getLastInsertId();
        this.addHistory(lastInsertId, userId, "Import", "申请导入数据: " + currNode.getName());


        // 4.保存 json 文件
        try (FileWriter writer = new FileWriter(uploadPath + saveJsonFilePath)) {
            writer.write(JSON.toJSONString(excelSheetDataList));
        } catch (IOException e) {
            return ApiResult.error("导入数据，写入文件时出错: " + e.getMessage());
        }

        return ApiResult.success();
    }


    /**
     * 检查表是否存在
     *
     * @param projectId
     * @param metadataNode
     * @param userId
     */
    public void checkTable(Integer projectId, MetadataNode metadataNode, Integer userId) {
        ProjectNode projectNode = this.projectNodeMapper.selectOne(ProjectNode.builder()
                .projectId(projectId)
                .metadataNodeName(metadataNode.getName())
                .build());

        // 如果节点不存在，就创建节点
        if (projectNode == null) {
            this.projectNodeMapper.insertSelective(ProjectNode.builder()
                    .projectId(projectId)
                    .metadataNodeId(metadataNode.getId())
                    .metadataNodeName(metadataNode.getName())
                    .metadataNodeProperties(metadataNode.getProperties())
                    .metadataNodePropertiesKey(metadataNode.getPropertiesKey())
                    .metadataId(metadataNode.getMetadataId())
                    .createdTime(new Date())
                    .updatedTime(new Date())
                    .dataTotal(1)
                    .build());
        }


        List<NodePropertiesItem> nodePropertiesItems = JSON.parseArray(metadataNode.getProperties(), NodePropertiesItem.class);
        String tableName = "md_project_node_data_" + projectId + "_" + metadataNode.getId();
        int count = projectMapper.checkTableExists(tableName);
        if (count == 0) {
            projectMapper.createTable(tableName, nodePropertiesItems);
        }
    }

    @Autowired(required = false)
    ProjectLogHistoryMapper projectLogHistoryMapper;


    public void addHistory(Integer projectLogId, Integer userId, String type, String content) {
        this.projectLogHistoryMapper.insertSelective(ProjectLogHistory.builder()
                .projectLogId(projectLogId)
                .userId(userId)
                .type(type)
                .content(content)
                .createdTime(new Date())
                .build());

        // 7.更新日志状态
        this.projectLogMapper.updateByPrimaryKeySelective(ProjectLog.builder()
                .id(projectLogId)
                .historyTotal(this.projectLogHistoryMapper.countByProjectLogId(projectLogId))
                .build());
    }


    public ApiResult submitDelete(List<Integer> idList, Integer projectId, Integer metadataNodeId, Integer userId) {
        Project project = this.checkProjectPower(projectId, userId, "Editor");

        // 1.查询项目节点
        ProjectNode projectNode = this.projectNodeMapper.selectOne(ProjectNode.builder()
                .projectId(projectId)
                .metadataNodeId(metadataNodeId)
                .build());
        if (projectNode == null) {
            return ApiResult.error("项目节点不存在");
        }
        List<String> conditionList = Arrays.asList("id in (" + String.join(",", idList.stream().map(String::valueOf).collect(Collectors.toList())) + ")");
        List<Map<String, Object>> nodeDataList = this.projectMapper.getNodeDataList(
                projectNode.getProjectId(), projectNode.getMetadataNodeId(),
                0, idList.size(), conditionList, " order by id desc");
        if (nodeDataList.size() != idList.size()) {
            return ApiResult.error("数据不存在");
        }

        // 2.备份删除数据
        List<NodePropertiesItem> tableFieldList = JSON.parseArray(projectNode.getMetadataNodeProperties(), NodePropertiesItem.class);
        List<String> headers = tableFieldList.stream().map(NodePropertiesItem::getLabel).collect(Collectors.toList());

        List<ExcelReaderUtil.ExcelSheetData> excelSheetDataList = new ArrayList<>();
        excelSheetDataList.add(ExcelReaderUtil.ExcelSheetData.builder()
                .headers(headers)
                .index(0)
                .tableFieldList(tableFieldList)
                .errorIndexList(new ArrayList<>())
                .errorPositionList(new ArrayList<>())
                .errorTotal(0)
                .tableData(nodeDataList)
                .name(projectNode.getMetadataNodeName())
                .tableName("md_project_node_data_" + projectId + "_" + projectNode.getMetadataNodeId())
                .build());

        String jsonData = JSON.toJSONString(excelSheetDataList);
        String saveJsonFilePath = "/import-file/" + Base62Encoder.generateUniqueKey() + ".json";
        String dataKey = DigestUtils.md5DigestAsHex(jsonData.getBytes());

        ProjectLog projectLog = this.projectLogMapper.selectOne(ProjectLog.builder()
                .projectId(projectNode.getProjectId())
                .dataKey(dataKey)
                .userId(userId)
                .build());
        if (projectLog != null) {
            return ApiResult.error("删除申请 已存在");
        }

        // 3.增加日志
        this.projectLogMapper.insertSelective(ProjectLog.builder()
                .userId(userId)
                .metadataName(project.getMetadataName())
                .metadataId(projectNode.getMetadataId())
                .projectName(project.getName())
                .projectId(projectNode.getProjectId())
                .projectName(project.getName())
                .projectNodeNames(projectNode.getMetadataNodeName())
                .type("Delete")
                .status(0)
                .refuseReason("")
                .file(saveJsonFilePath)
                .isUserRead(1)
                .handlerUserId(this.projectUserMapper.getBelongUserId(projectNode.getProjectId()))
                .isHandlerUserRead(0)
                .dataKey(dataKey)
                .createdTime(new Date())
                .build());

        Integer lastInsertId = this.projectLogMapper.getLastInsertId();
        this.addHistory(lastInsertId, userId, "Import", "申请导入数据: " + projectNode.getMetadataNodeName());


        // 4.保存 json 文件
        try (FileWriter writer = new FileWriter(uploadPath + saveJsonFilePath)) {
            writer.write(JSON.toJSONString(excelSheetDataList));
        } catch (IOException e) {
            return ApiResult.error("删除数据，写入文件时出错: " + e.getMessage());
        }

        return ApiResult.success();
    }


    /**
     * 检查项目权限
     *
     * @param projectId
     * @param userId
     * @param role
     * @return
     */
    public Project checkProjectPower(Integer projectId, Integer userId, String role) {
        Project project = this.projectMapper.selectByPrimaryKey(projectId);
        if (project == null) {
            throw new ServiceException("项目不存在");
        }

        // 项目数据员: 项目成员
        if (role.equals("Editor")) {
            if (this.projectUserMapper.isProjectUser(projectId, userId) == 0) {
                throw new ServiceException("没有项目权限");
            }
        }

        // 项目所有者
        if (role.equals("Owner")) {
            if (!this.projectUserMapper.getBelongUserId(projectId).equals(userId)) {
                throw new ServiceException("没有项目权限");
            }
        }
        return project;
    }

    /**
     * 回滚数据
     *
     * @param projectLogId
     * @param userId
     * @return
     */
    public ApiResult rollBack(Integer projectLogId, Integer userId) {
        ProjectLog projectLog = this.projectLogMapper.selectByPrimaryKey(ProjectLog.builder()
                .id(projectLogId)
                .handlerUserId(userId)
                .isRollBack(0)
                .status(1)
                .build());
        if (projectLog == null) {
            return ApiResult.error("日志不存在");
        }
        if (projectLog.getIsRollBack() == 1) {
            return ApiResult.error("日志已回滚");
        }
        projectLog.setIsRollBack(1);
        this.projectLogMapper.updateByPrimaryKeySelective(projectLog);

        // 1.导入数据
        if (projectLog.getType().equals("Import")) {
            // 删除-导入数据
            List<ExcelReaderUtil.ExcelSheetData> excelSheetDataList = ExcelReaderUtil.getFileContent(uploadPath + projectLog.getFile());
            for (ExcelReaderUtil.ExcelSheetData excelSheetData : excelSheetDataList) {
                this.projectMapper.deleteAllDataByProjectLogId(excelSheetData.getTableName(), projectLog.getId());
            }

            List<Integer> ids = this.projectMapper.getProjectLogIdList(excelSheetDataList.get(0).getTableName(), projectLogId);

            // 2.添加回滚日志
            this.userOperationLogService.insert(UserOperationLog.builder()
                    .userId(userId)
                    .username(userMapper.selectByPrimaryKey(userId).getUsername())
                    .type("ProjectNodeData.Delete")
                    .content(projectLog.getProjectName() + " 插入回滚(删除)数据: " + projectLog.getProjectNodeNames())
                    .params(JSON.toJSONString(this.getMetadataNodeIdByNames(projectLog.getProjectId(), projectLog.getProjectNodeNames(), ids)))
                    .createdTime(new Date())
                    .build());
        }

        // 2.删除数据
        if (projectLog.getType().equals("Delete")) {
            // 恢复删除数据
            this.insertAllData(projectLog, projectLog.getUserId());

            // 2.添加回滚日志
            this.userOperationLogService.insert(UserOperationLog.builder()
                    .userId(userId)
                    .username(userMapper.selectByPrimaryKey(userId).getUsername())
                    .type("ProjectNodeData.Insert")
                    .content(projectLog.getProjectName() + " 删除回滚(恢复)数据: " + projectLog.getProjectNodeNames())
                    .params(JSON.toJSONString(this.getMetadataNodeIdByNames(projectLog.getProjectId(), projectLog.getProjectNodeNames(), new ArrayList<>())))
                    .createdTime(new Date())
                    .build());
        }

        this.updateDataTotal(projectLog.getId(), projectLog.getUserId());

        return ApiResult.success();
    }


    public ProjectLogChangeItem getMetadataNodeIdByNames(Integer projectId, String metadataNodeNames, List<Integer> ids) {
        Example example = new Example(ProjectNode.class);
        example.createCriteria().andEqualTo("projectId", projectId).andIn("metadataNodeName", Arrays.asList(metadataNodeNames.split(",")));
        List<ProjectNode> projectNodes = this.projectNodeMapper.selectByExample(example);
        return ProjectLogChangeItem.builder()
                .projectId(projectId)
                .metadataNodeId(projectNodes.stream().map(ProjectNode::getMetadataNodeId).collect(Collectors.toList()))
                .ids(ids)
                .build();
    }

    public void updateDataTotal(Integer projectLogId, Integer userId) {
        ProjectLog projectLog = this.projectLogMapper.selectByPrimaryKey(projectLogId);
        Example example = new Example(ProjectNode.class);
        example.createCriteria()
                .andEqualTo("projectId", projectLog.getProjectId())
                .andIn("metadataNodeName", Arrays.asList(projectLog.getProjectNodeNames().split(",")));
        List<ProjectNode> projectNodes = this.projectNodeMapper.selectByExample(example);
        for (ProjectNode projectNode : projectNodes) {
            int nodeDataTotal = this.projectMapper.getNodeDataTotal(projectNode.getProjectId(), projectNode.getMetadataNodeId(), Arrays.asList("1=1"));
            this.projectNodeMapper.updateByPrimaryKeySelective(ProjectNode.builder()
                    .id(projectNode.getId())
                    .dataTotal(nodeDataTotal)
                    .build());

            // 节点数据总数更新
            int nodeCount = this.projectNodeMapper.getDataTotalByName(projectNode.getMetadataId(), projectNode.getMetadataNodeName());
            this.metadataNodeService.updateDataTotal(projectNode.getMetadataNodeName(), projectNode.getMetadataId(), nodeCount, userId);
        }

        // 更新-项目-数据总数
        this.projectService.updateDataTotal(projectLog.getProjectId(), userId);
        // 更新-数据库-数据总数
        this.databaseService.updateDataTotal(projectLog.getProjectId(), userId);
        // 更新-元数据-数据总数
        this.metadataService.updateDataTotal(projectLog.getMetadataId(), userId);

    }

    @Autowired
    ProjectService projectService;
    @Autowired
    MetadataNodeService metadataNodeService;
    @Autowired
    MetadataService metadataService;
    @Autowired
    DatabaseService databaseService;


}
